/****
 * 渲染器核心
 */
const Text = Symbol('文本节点')
const Comment = Symbol('注释节点')
const Fragment = Symbol('片段')
function createRenderer(options) {
  const { createElement, insert, setElementText, patchProps, createText, setText } = options
  // 渲染
  function render(vnode, container) {
    if (vnode) {
      // 更新节点
      patch(container._vnode, vnode, container)
    } else {
      // 卸载节点
      if (container._vnode) {
        unmount(vnode, container)
      }
    }
    container._vnode = vnode
  }
  // 激活
  function hydrate(vnode, container) {}
  // 打补丁
  function patch(n1, n2, container, anchor) {
    // 新旧节点类型对比
    if (n1.type !== n2.type) {
      unmount(n1)
      n1 = null
    }
    const { type } = n2
    if (typeof type === 'string') {
      // 旧节点不存在，挂载
      if (!n1) {
        mountElement(n2, container, anchor)
      } else {
        // 打补丁
        patchElement(n1, n2)
      }
    } else if (type === Text) {
      // 文本
      if (!n1) {
        const el = (e2.el = createText(n2.children))
        insert(el, container)
      } else {
        const el = (e2.el = n1.el)
        if (n2.children !== n1.children) {
          setText(el, n2.children)
        }
      }
    } else if (type === Fragment) {
      // 片段
      if (!n1) {
        n2.children.forEach((c) => patch(null, c, container))
      } else {
        patchChildren(n1, n2, container)
      }
    } else if (typeof type === 'object') {
      // 组件
    }
  }
  // 挂载元素
  function mountElement(vnode, container, anchor) {
    // 在vnode和node间建立联系
    const el = (vnode.el = createElement(vnode.type))
    // 子节点更新
    if (typeof vnode.children === 'string') {
      setElementText(el, vnode.children)
    } else if (Array.isArray(vnode.children)) {
      // 批量补丁
      vnode.children.forEach((child) => {
        patch(null, child, el)
      })
    }
    // 属性更新
    if (vnode.props) {
      for (const key in vnode.props) {
        patchProps(el, key, null, vnode.props[key])
      }
    }
    insert(el, container, anchor)
  }

  /**
   * 对指定节点打补丁
   * @param n1 老节点
   * @param n2 新节点
   */
  function patchElement(n1, n2) {
    const el = (n2.el = n1.el)
    const oldProps = n1.props
    const newProps = n2.props
    // 更新props
    for (const key in newProps) {
      if (newProps[key] !== oldProps[key]) {
        // 对某个元素进行打补丁
        patchProps(el, key, oldProps, newProps)
      }
    }
    // 卸载老的props
    for (const key in oldProps) {
      if (!(key in newProps)) {
        // 新值不存在。卸载属性
        patchProps(el, key, oldProps[key], null)
      }
    }
    // 更新children
    patchChildren(n1, n2, el)
  }

  /**
   * 更新子节点（空节点、文本节点、一组子节点）
   * @param n1 老节点
   * @param n2 新节点
   * @param container 父元素
   */
  function patchChildren(n1, n2, container) {
    // 新节点是文本节点
    if (typeof n2.children === 'string') {
      if (Array.isArray(n1.children)) {
        n1.children.forEach((c) => unmount(c))
      }
      setElementText(container, n2.children)
    } else if (Array.isArray(n2.children)) {
      // 新节点是一组子节点
      if (Array.isArray(n1.children)) {
        // patchMetaChildren(n1, n2, container)
        // patchSimpleChildren(n1,n2,container)
        patchKeyedChildren(n1, n2, container)
      } else {
        // 不存在或文本节点，直接干掉
        setElementText(container, '')
        // 重新更新节点
        n2.children.forEach((c) => patch(null, c, container))
      }
    } else {
      // 新节点不存在
      if (Array.isArray(n1.children)) {
        // 组卸载
        n1.children.forEach((c) => unmount(c))
      } else if (typeof n1.children === 'string') {
        // 文本卸载
        setElementText(container, '')
      }
    }
    // 取消挂载
    function unmount(vnode, container) {
      if (vnode.type === Fragment) {
        vnode.children.forEach((c) => unmount(c))
      }
      const parent = vnode.el.parentNode
      if (parent) {
        parent.removeChild(vnode.el)
      }
    }

    /**
     * 暴力diff
     * @param oldChildren
     * @param newChildren
     * @param container
     */
    function patchMetaChildren(oldChildren, newChildren, container) {
      const oldLen = oldChildren.length
      const newLen = newChildren.length
      const commonLength = Math.min(oldLen, newLen)
      // 公共长度，直接打补丁
      for (let i = 0; i < commonLength; i++) {
        patch(oldChildren[i], newChildren[i])
      }
      // 新节点长度更大
      if (newLen > oldLen) {
        for (let i = commonLength; i < newLen; i++) {
          patch(null, newChildren[i], container)
        }
      } else if (oldLen > newLen) {
        // 新节点长度更小
        for (let i = commonLength; i < oldLen; i++) {
          unmount(oldChildren[i], container)
        }
      }
    }

    /**
     * 简单diff
     * @param n1
     * @param n2
     * @param container
     */
    function patchSimpleChildren(n1, n2, container) {
      const oldChildren = n1.children
      const newChildren = n2.children
      let lastIndex = 0
      for (let i = 0; i < newChildren.length; i++) {
        const newVNode = newChildren[i]
        let j = 0
        let find = false
        for (j = 0; j < oldChildren.length; j++) {
          const oldNode = oldChildren[j]
          if (newVNode.key === oldNode.key) {
            find = true
            patch(oldNode, newVNode, container)
            if (j < lastIndex) {
              const prevVNode = newChildren[i - 1]
              if (prevVNode) {
                const anchor = prevVNode.el.nextSibling
                insert(newVNode.el, container, anchor)
              }
            } else {
              lastIndex = j
            }
            break
          }
        }
        if (!find) {
          const prevVNode = newChildren[i - 1]
          let anchor = null
          if (prevVNode) {
            anchor = prevVNode.el.nextSibling
          } else {
            anchor = container.firstChild
          }
          patch(null, newVNode, container, anchor)
        }
      }
      for (let i = 0; i < oldChildren.length; i++) {
        const oldVNode = oldChildren[i]
        const has = newChildren.find((vnode) => vnode.key === oldVNode.key)
        if (!has) {
          unmount(oldVNode, container)
        }
      }
    }
    /**
     * 双端diff
     * @param n1 老节点数组
     * @param n2 新节点数据
     * @param container 容器
     */
    function patchKeyedChildren(n1, n2, container) {
      const oldChildren = n1.children
      const newChildren = n2.children
      let oldStartIdx = 0
      let oldEndIdx = oldChildren.length - 1
      let newStartIdx = 0
      let newEndIdx = newChildren.length - 1
      let oldStartVNode = oldChildren[oldStartIdx]
      let oldEndVNode = oldChildren[oldEndIdx]
      let newStartVNode = newChildren[newStartIdx]
      let newEndVNode = newChildren[newEndIdx]
      while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
        if (!oldStartVNode) {
          oldStartVNode = oldChildren[++oldStartIdx]
        } else if (!oldEndVNode) {
          oldEndVNode = oldChildren[--oldEndIdx]
        } else if (oldStartVNode.key === newStartVNode.key) {
          // 老新头头
          patch(oldStartVNode, newStartVNode, container)
          oldStartVNode = oldChildren[++oldStartIdx]
          newStartVNode = newChildren[++newStartIdx]
        } else if (oldEndVNode.key === newEndVNode.key) {
          // 老新尾尾
          patch(oldEndVNode, newEndVNode, container)
          oldEndVNode = oldChildren[--oldEndIdx]
          newEndVNode = newChildren[--newEndIdx]
        } else if (oldStartVNode.key === newEndVNode.key) {
          // 老头新尾
          patch(oldStartVNode, newEndVNode, container)
          insert(oldStartVNode.el, container, oldEndVNode.el.nextSibling)
          oldStartVNode = oldChildren[++oldStartIdx]
          newEndVNode = newChildren[--newEndIdx]
        } else if (oldEndVNode.key === newStartVNode.key) {
          // 新头老尾
          patch(oldEndVNode, newStartVNode, container)
          insert(oldEndVNode.el, container, oldStartVNode.el)
          oldEndVNode = oldChildren[--oldEndIdx]
          newStartVNode = newChildren[++newStartIdx]
        } else {
          // 上面的比较都没有比较到
          const idxInOld = oldChildren.findIndex((node) => node.key === newStartVNode.key)
          if (idxInOld > 0) {
            const vnodeToMove = oldChildren[idxInOld]
            patch(vnodeToMove, newStartVNode, container)
            insert(vnodeToMove.el, container, oldStartVNode.el)
            oldChildren[idxInOld] = undefined
          } else {
            // 新增不存在的元素
            patch(null, newStartVNode, container, oldStartVNode.el)
          }
          newStartVNode = newChildren[++newStartIdx]
        }
      }
      // 挂载遗漏的节点
      if (oldEndIdx < oldStartIdx && newStartIdx <= newEndIdx) {
        for (let i = newStartIdx; i <= newEndIdx; i++) {
          const anchor = newChildren[newEndIdx + 1] ? newChildren[newEndIdx + 1].el : null
          patch(null, newChildren[i], container, anchor)
        }
      } else if (newEndIdx < newStartIdx && oldStartIdx <= oldEndIdx) {
        // 删除遗漏的节点
        for (let i = oldStartIdx; i <= oldEndIdx; i++) {
          unmount(oldChildren[i])
        }
      }
    }
  }

  return {
    render,
  }
}

/**
 * 是否应该作为值被设置
 * @param el 真实dom
 * @param key 属性名
 * @param value 属性值
 * @returns {boolean}
 */
function shouldSetAsProps(el, key, value) {
  if (key === 'form' && el.tagName === 'input') return false
  return key in el
}
const render = createRenderer({
  // 创建元素
  createElement(tag) {
    return document.createElement(tag)
  },
  // 设置元素文本
  setElementText(el, text) {
    el.textContent = text
  },
  // 插入元素
  insert(el, parent, anchor = null) {
    parent.insertBefore(el, anchor)
  },
  createText(text) {
    return document.createTextNode(text)
  },
  setText(el, text) {
    el.nodeValue = text
  },
  // 设置属性
  patchProps(el, key, prevValue, nextValue) {
    // 事件
    if (/^on/.test(key)) {
      // 获取事件操纵对象
      let invokers = el.el_vei || (el.vei = {})
      // 拿到指定key的事件
      let invoker = invokers[key]
      const name = key.slice(2).toLowerCase()
      if (nextValue) {
        if (!invoker) {
          invoker = el._vel[key] = (e) => {
            // 如果事件发生的时候比事件注册的时间晚，则不在执行
            if (e.timeStamp < invoker.attached) return
            // 如果事件处理是数组，则循环执行
            if (Array.isArray(invoker.value)) {
              invoker.value.forEach((fn) => fn(e))
            } else {
              invoker.value(e)
            }
          }
          invoker.value = nextValue
          // 设置注册时间
          invoker.attached = performance.now()
          el.addEventListener(name, invoker)
        } else {
          // 更新事件值
          invoker.value = nextValue
        }
      } else if (invoker) {
        // 移除之前注册的事件
        el.removeEventListener(name, invoker)
      }
      // prevValue && el.removeEventListener(name, prevValue)
      // el.addEventListener(name, nextValue)
    }
    // 类单独绑定
    else if (key === 'class') {
      el.className = nextValue || ''
    } else if (shouldSetAsProps(el, key, nextValue)) {
      // 正常值正常设置
      const type = typeof el[key]
      // 如果是boolean类型的值，默认设置为true
      if (type === 'boolean' && nextValue === '') {
        el[key] = true
      } else {
        // 其他正常设置
        el[key] = nextValue
      }
    } else {
      // 兜底设置
      el.setAttribute(key, nextValue)
    }
  },
})
